package fachlogik;

import java.sql.Time;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.JoinColumn;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;

import algorithm.FinalTool;
import algorithm.MostImportantFirst;
import algorithm.ShortestAppointmentFirst;

import entities.Appointment;
import entities.Calendar;
import entities.Category;
import entities.Person;
import exceptions.IllegalAppointmentException;
import exceptions.KeinFreierTermin;

public class Administration {

	private EntityManager em;
	private static Administration instance;

	// Person, die sich anmeldet
	private Person user;

	// Enthaelt alle Calendar des Users
	private List<Calendar> userCalendar = new ArrayList<>();

	// Enthaelt alle Appointments eines bestimmten Calendars
	private List<Appointment> calendarAppointments = new ArrayList<>();

	// Enthaelt alle Kategorien eines bestimmten Calendars
	private List<Category> calendarCategories = new ArrayList<>();

	// Enthaelt alle Appointments, die rescheduled werden duerfen
	private List<Appointment> allrescheduledAppointments = new ArrayList<>();

	private JInternalFrame frame;
	private List<Appointment> timespanOfDatesToReschedule;
	private Date rescheduleErlaubtVon;
	private Date rescheduleErlaubtBis;

	public Administration() {
		em = Persistence.createEntityManagerFactory("Scheduler")
				.createEntityManager();
	}

	public static Administration getInstance() {
		if (instance == null)
			instance = new Administration();
		return instance;
	}

	public void reschedule(Date von, Date bis,
			Appointment appointmentToReschedule) throws KeinFreierTermin, IllegalAppointmentException {
		FinalTool tool = new FinalTool();
		SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy HH:mm");
		Date d = tool.findGap(appointmentToReschedule, von, bis);
		if (d != null)
			JOptionPane.showMessageDialog(null, df.format(d));
		else
			JOptionPane.showMessageDialog(null, "Date ist null!");
		long laenge = appointmentToReschedule.getLength();
		appointmentToReschedule.setBegin(d);
		appointmentToReschedule.setEnd(new Date(d.getTime() + laenge));
		appointmentToReschedule.setAppointmentPriority(appointmentToReschedule.getAppointmentPriority()+1);
		addOrEditAppointment(appointmentToReschedule);
	}

	public void mostImportantAppointment(Date von, Date bis,
			List<Appointment> appointmentsToReschedule)
			throws IllegalAppointmentException {

		System.out.println("MostImportantAppointment");
		MostImportantFirst miaf = new MostImportantFirst();
		miaf.rescheduleMostIpApp();

	}

	public void shortestAppointmentFirst(Date von, Date bis,
			List<Appointment> appointmentsToReschedule)
			throws IllegalAppointmentException, KeinFreierTermin {
		System.out.println("ShortestAppointment");
		new ShortestAppointmentFirst(von, bis, appointmentsToReschedule);
	}

	public List<Appointment> abgelaufeneAppointments(Calendar c) {
		if (c != null) {
			Date letzteAnmeldung = user.getLetzteAnmeldung();
			if (letzteAnmeldung != null) {
				Date jetzt = new Date();

				List<Appointment> appointments = getCalendarAppointments(c);
				List<Appointment> abgelaufeneAppointments = new ArrayList<>();

				for (Appointment a : appointments) {
					if (a.getEnd().after(letzteAnmeldung)
							&& a.getEnd().before(jetzt)) {
						abgelaufeneAppointments.add(a);
					}
				}
				return abgelaufeneAppointments;
			}
			return null;
		}
		return null;
	}

	public List<Appointment> appointmentsSortieren(List<Appointment> liste) {
		List<Appointment> appointmentsSortiert = new ArrayList<Appointment>();
		if (liste != null) {
			while (!liste.isEmpty()) {
				Appointment a = sortieren(liste);
				liste.remove(a);
				appointmentsSortiert.add(a);
			}
			return appointmentsSortiert;
		}
		return null;
	}

	// gehoert zur Methode getAllRescheduledAppointments() und sortiert nach dem
	// fruehesten Appointment
	private Appointment sortieren(List<Appointment> Appointments) {
		Appointment c = null;
		System.out.println(Appointments.size());
		int zahl = 0;
		for (int i = 0; i < Appointments.size(); i++) {
			/*
			 * - Return value is 0 if both dates are equal. - Return value is
			 * greater than 0 , if first date is after the second date argument.
			 * - Return value is less than 0, if first date is before the second
			 * date argument.
			 */
			zahl = i;
			Appointment a = null;
			Appointment b = null;
			if (i == 0) {
				if (i + 1 < Appointments.size()) {
					a = Appointments.get(i);
					b = Appointments.get(i + 1);
					int vergleich = a.getBegin().compareTo(b.getBegin());

					if (vergleich > 0) // a nach b
					{
						c = b;
					}
					if (vergleich < 0) // a vor b
					{
						c = a;
					}
				}
			} else {
				a = Appointments.get(i);
				int vergleich2 = a.getBegin().compareTo(c.getBegin());
				if (vergleich2 < 0) // a vor c
				{
					c = a;
				}
			}
		}
		if (c == null)
			c = Appointments.get(zahl);
		if (c != null)
			System.out.println(c.getName());
		else
			JOptionPane.showMessageDialog(null, "c ist null");
		return c;
	}

	// �berprueft, ob die der uebergebene Username und das uebergebene
	// Password in
	// der Datenbank schon existiert
	public boolean login(Person p) {
		String sql = "SELECT p FROM Person p WHERE p.username = :a AND p.password = :b";
		Query q = em.createQuery(sql);
		q.setParameter("a", p.getUsername());
		q.setParameter("b", p.getPassword());
		if (!q.getResultList().isEmpty()) {
			List<Person> list = q.getResultList();
			for (Person person : list) {
				user = person;
			}
			return true;
		} else
			return false;
	}

	public List<Calendar> getUserCalendar() {
		if (user != null) {
			String sql = "SELECT c FROM Calendar c WHERE c.person = :a";
			Query q = em.createQuery(sql);
			q.setParameter("a", user);
			userCalendar = q.getResultList();
			return userCalendar;
		}
		JOptionPane
				.showMessageDialog(frame,
						"User ist null, somit koennen auch keine Calender ausgegeben werden!");
		return null;
	}

	public void setUserCalendar(List<Calendar> userCalendar) {
		this.userCalendar = userCalendar;
	}

	public List<Appointment> getCalendarAppointments(Calendar c) {
		if (c != null) {
			String sql = "SELECT a FROM Appointment a WHERE a.calendar = :b";
			Query q = em.createQuery(sql);
			q.setParameter("b", c);
			if (q.getResultList() != null)
				c.setAppointmentList(q.getResultList());
			calendarAppointments = q.getResultList();
			return calendarAppointments;
		}
		return null;
	}

	public void test(Calendar c) {
		SimpleDateFormat sf = new SimpleDateFormat("dd.MM.yyyy HH:mm");
		for (Appointment a : getCalendarAppointments(c)) {
			System.out.println(a.getName());
			System.out.println(sf.format(a.getBegin()));
			System.out.println(sf.format(a.getEnd()));
		}
	}

	public List<Appointment> getAllAppointments() {
		String sql = "SELECT a FROM Appointment a";
		Query q = em.createQuery(sql);
		return q.getResultList();
	}

	public void setCalendarAppointments(List<Appointment> calendarAppointments) {
		this.calendarAppointments = calendarAppointments;
	}

	public List<Category> getCalendarCategories(Calendar c) {
		String sql = "SELECT c FROM Category c WHERE c.calendar = :a";
		Query q = em.createQuery(sql);
		q.setParameter("a", c);
		calendarCategories = q.getResultList();
		return calendarCategories;
	}

	// SQL Statement waere besser
	public List<Appointment> rescheduleBetweenTimespan(Date Anfang, Date Ende,
			Calendar c) {
		List<Appointment> alleAppointments = getCalendarAppointments(c);
		timespanOfDatesToReschedule = new ArrayList<>();

		for (Appointment a : alleAppointments) {
			if (a.getBegin() != null && a.getEnd() != null) {
				if (a.getBegin().getTime() >= Anfang.getTime()
						&& a.getEnd().getTime() <= Ende.getTime())
					timespanOfDatesToReschedule.add(a);
			}
		}
		if (!timespanOfDatesToReschedule.isEmpty())
			return appointmentsSortieren(timespanOfDatesToReschedule);
		else
			JOptionPane
					.showMessageDialog(null,
							"Es gibt keine Termine auf die Ruecksicht genommen werden muss!");
		return new ArrayList<Appointment>();
	}

	public List<Appointment> gettimespanOfDatesToReschedule() {
		return timespanOfDatesToReschedule;
	}

	public Date getRescheduleErlaubtVon() {
		return rescheduleErlaubtVon;
	}

	public void setRescheduleErlaubtVon(Date rescheduleErlaubtVon) {
		this.rescheduleErlaubtVon = rescheduleErlaubtVon;
	}

	public Date getRescheduleErlaubtBis() {
		return rescheduleErlaubtBis;
	}

	public void setRescheduleErlaubtBis(Date rescheduleErlaubtBis) {
		this.rescheduleErlaubtBis = rescheduleErlaubtBis;
	}

	public void setCalendarCategories(List<Category> calendarCategories) {
		this.calendarCategories = calendarCategories;
	}

	public Person getUser() {
		return user;
	}

	public void setUser(Person user) {
		this.user = user;
	}

	public List<Appointment> getAllrescheduledAppointments() {
		List<Appointment> list = new ArrayList<>();
		for (Appointment a : calendarAppointments) {
			if (a.getReschedulingAllowed())
				list.add(a);
		}
		allrescheduledAppointments = list;
		return allrescheduledAppointments;
	}

	public void setAllrescheduledAppointments(
			List<Appointment> allrescheduledAppointments) {
		this.allrescheduledAppointments = allrescheduledAppointments;
	}

	public List<Appointment> allDayAppointments(Date day,
			Calendar selectedCalendar) {
		String jdql = "SELECT a from Appointment a WHERE (FUNC('DAY', a.begin) = FUNC('DAY', :b) AND FUNC('MONTH', a.begin) = FUNC('MONTH', :b) AND FUNC('YEAR', a.begin) = FUNC('YEAR', :b)) AND a.calendar = :c AND a.calendar.person = :d";
		Query q = em.createQuery(jdql);
		q.setParameter("b", day);
		q.setParameter("c", selectedCalendar);
		q.setParameter("d", user);
		return q.getResultList();
	}

	// ----------------------------------------------------Calendar,
	// Appointment, Category -- add, edit, delete
	// ---------------------------------

	public void addPerson(Person p) {
		String sql = "SELECT p from Person p WHERE p.username = :a";
		Query q = em.createQuery(sql);
		q.setParameter("a", p.getUsername());
		if (q.getResultList().isEmpty()) {
			em.getTransaction().begin();
			em.persist(p);
			em.getTransaction().commit();
		} else
			JOptionPane.showMessageDialog(frame,
					"Ein User mit diesem Usernamen existiert schon!");
	}

	public void editPerson(Person p) {
		em.getTransaction().begin();
		em.persist(p);
		em.getTransaction().commit();
	}

	public void removePerson(Person p) throws IllegalAppointmentException {
		for (Calendar cal : p.getCalendarliste()) {
			for (Appointment a : cal.getAppointmentList()) {
				removeAppointment(a);
			}
			for (Category c : cal.getCategoryList()) {
				removeCategory(c);
			}
			removeCalendar(cal);
		}
		em.getTransaction().begin();
		em.remove(p);
		em.getTransaction().commit();
	}

	public void addCalendar(Calendar cal) {
		boolean existiert = false;
		for (Calendar c : userCalendar) {
			if (!c.getName().equals(cal.getName()))
				existiert = false;
			else
				existiert = true;
		}
		if (!existiert) {
			em.getTransaction().begin();
			userCalendar.add(cal);
			em.persist(cal);
			em.getTransaction().commit();
		} else
			JOptionPane.showMessageDialog(frame,
					"Es existiert schon ein Kalender mit dem gleichen Namen!");
	}

	public void editCalendar(Calendar cal) {
		em.getTransaction().begin();
		em.persist(cal);
		em.getTransaction().commit();
	}

	public void removeCalendar(Calendar cal) throws IllegalAppointmentException {
		if (cal != null) {
			for (Appointment a : cal.getAppointmentList()) {
				removeAppointment(a);
			}
			cal.setAppointmentList(null);
			for (Category c : cal.getCategoryList()) {
				removeCategory(c);
			}
			cal.setCategoryList(null);
			em.getTransaction().begin();
			em.remove(cal);
			em.getTransaction().commit();
		}
	}

	public void addOrEditAppointment(Appointment a)
			throws IllegalAppointmentException {
		if (calendarAppointments.contains(a)) {
			if (a.getBegin().getTime() <= a.getEnd().getTime()) {
				em.getTransaction().begin();
				em.persist(a);
				em.getTransaction().commit();
			} else
				throw new IllegalAppointmentException(a);
		} else {
			// if(a.getBegin().getTime() <= a.getEnd().getTime()){
			em.getTransaction().begin();
			em.persist(a);
			em.getTransaction().commit();
			calendarAppointments.add(a);
			// }
			// else
			// throw new IllegalAppointmentException(a);
		}
	}

	public void removeAppointment(Appointment a) {
		if (a.getCategory() != null) {
			a.setCategory(null);
		}
		if (a.getCalendar() != null) {
			a.setCalendar(null);
		}
		em.getTransaction().begin();
		em.remove(a);
		em.getTransaction().commit();
	}

	public void addCategory(Category c) {
		boolean existiert = false;
		for (Category cat : calendarCategories) {
			if (cat.getName().equals(c.getName()))
				existiert = true;
			int a = cat.getAnfangszeit().compareTo(c.getAnfangszeit());
			int e = cat.getEndzeit().compareTo(c.getEndzeit());
			if (a == 0 && e == 0)
				existiert = true;
		}
		if (!existiert) {
			em.getTransaction().begin();
			calendarCategories.add(c);
			em.persist(c);
			em.getTransaction().commit();
		} else
			JOptionPane.showMessageDialog(null,
					"Diese Kategorie existiert bereits!");
	}

	public void editCategory(Category c) {
		for (Category cat : calendarCategories) {
			if (cat.getId() == c.getId()) {
				cat = c;
				em.getTransaction().begin();
				em.persist(c);
				em.getTransaction().commit();
				return;
			}
		}
	}

	public void removeCategory(Category c) throws IllegalAppointmentException {
		for (Appointment a : c.getAppointments()) {
			a.setCategory(null);
			addOrEditAppointment(a);
		}
		c.setAppointments(null);
		em.getTransaction().begin();
		em.remove(c);
		em.getTransaction().commit();
	}

	public List<Person> getAllUsers() {
		return em.createQuery("SELECT p FROM Person p").getResultList();
	}

	public boolean userExists(String s) {
		if (!em.createQuery("SELECT p FROM Person P WHERE p.username = :a")
				.setParameter("a", s).getResultList().isEmpty())
			return true;
		else
			return false;
	}
}
